/*
    日志落地模块的实现：
        1、抽象落地父类
        2、根据不同的落地方向派生子类
        3、用工厂模式分离创建与表示
*/

#ifndef __M_SINK_H__
#define __M_SINK_H__

#include "util.hpp"
#include <iostream>
#include <memory>
#include <fstream>
#include <cassert>
#include <sstream>
#include <cstring>
using namespace std;

namespace log{
    class LogSink{
    public:
        using ptr = shared_ptr<LogSink>;

        virtual ~LogSink(){

        }

        virtual void writeLog(const char *data, size_t len) = 0;
    };

    //落地方向：标准输出、指定文件、滚动文件（大小滚动）
    //标准输出
    class outSink : public LogSink{
    public:
        //将日志消息写入标准输出
        void writeLog(const char *data, size_t len) override{
            //从data开始写入len个字符
            cout.write(data, len);
            assert(cout.good());
        }
    };

    //指定文件
    class FileSink : public LogSink{
    public:
        //传入文件路径，并打开文件，将操作管理起来
        FileSink(const string &path):_path(path){
            //1.创建日志文件所在的目录
            util::File::createDirectory(util::File::path(_path));

            //2.创建并打开日志文件
            //二进制追加
            _ofs.open(_path, ios::binary | ios::app);
            assert(_ofs.is_open());
        }

        //数据写入文件
        void writeLog(const char *data, size_t len) override{
            _ofs.write(data, len);
            assert(_ofs.good());
        }

    private:
        string _path;   //文件路径
        ofstream _ofs;  //操作句柄
    };

    //滚动文件（大小滚动）
    class RollSink_size : public LogSink{
    public:
        //传入文件路径，并打开文件，将操作管理起来
        RollSink_size(const string &basename, size_t maxsize):_basename(basename), _maxsize(maxsize), _cursize(0), _num(0){
            
            //1.构造实际的日志文件名
            string path = createNewFile();
            
            //2.创建日志文件所在的目录
            util::File::createDirectory(util::File::path(path));

            // 3.创建并打开日志文件
            // 二进制追加
            _ofs.open(path, ios::binary | ios::app);
            assert(_ofs.is_open());
        }

        //写入前需判断文件大小，超过指定大小则切换文件
        void writeLog(const char *data, size_t len) override{
            //不超过指定大小直接写入，超过指定大小新建文件并切换
            if(_cursize < _maxsize){
                _ofs.write(data, len);
                assert(_ofs.good());
                _cursize += len;
            }
            else{
                //1.关闭原文件
                _ofs.close();
                //2.构造新文件名
                string newpath = createNewFile();
                //3.创建并打开新文件
                _ofs.open(newpath, ios::binary | ios::app);
                assert(_ofs.is_open());
                //4.每次创建新文件后对已有大小进行清零
                _cursize = 0;
                //还有一个问题：如果所有日志消息都在一秒内写完了，那么就会堆积在同一个文件中
                //  因为时间相同，每次open的都是同名文件，刚关闭的文件又被open了
                //解决方法：给同名文件编号
            }
        }
        
    private:
        //判断文件大小，超过指定大小则切换文件
        string createNewFile(){
            //1.获取系统时间戳
            time_t time = util::Date::getTime();
            
            //2.转化时间戳
            tm t;
            //将时间戳表示的时间存放到时间结构体中
            localtime_r(&time, &t);
            // string filename = _basename;
            // filename + to_string(t.tm_year)+to_string...

            stringstream filename;
            filename << _basename << t.tm_year + 1900 << "-" 
                     << t.tm_mon + 1 << "-" 
                     << t.tm_mday << "_" 
                     << t.tm_hour << ":" 
                     << t.tm_min << ":" 
                     << t.tm_sec << "-"  
                     << _num++ << ".log";

            return filename.str();
        }

    private:
        string _basename;   //基础文件名base+扩展文件名（以时间生产time）=实际文件名 -- ./log/base_time1
        ofstream _ofs;      //操作句柄
        size_t _maxsize;    //文件的最大大小，超过就要切换文件
        size_t _cursize;    //当前文件大小 -- 获取文件大小的系统接口开销大，我们手动记录
        size_t _num;        //文件名称计数器，用于区分同一秒中产生的同名文件
    };


    //时间滚动
    //时间戳 / 时间间隔 = 时间段序号
    enum TimeGap{
        GAP_SECOND = 1,
        GAP_MINUNTE = 60,
        GAP_HOUR = 3600,
        GAP_DAY = 24 * 3600
    };

    class RollSink_time : public LogSink{
    public:
        //传入文件路径，并打开文件，将操作管理起来
        RollSink_time(const string &basename, TimeGap gap):_basename(basename), _gap(gap){

            //1、设置时间段序号
            _gapnum = util::Date::getTime() / _gap;
            //2、构造文件名
            string path = createNewFile();
            //3、创建文件所在路径
            util::File::createDirectory(util::File::path(path));
            //4、打开文件
            _ofs.open(path, ios::binary | ios::app);
            assert(_ofs.is_open());

        }

        //写入数据前需判断当前时间是否是当前文件的时间段，不是则需要切换文件
        void writeLog(const char *data, size_t len) override{
            //1、获取系统时间
            time_t time = util::Date::getTime();

            //2、比较时间(时间戳 / 时间间隔 = 时间段序号)
            if((time / _gap) != _gapnum){
                _ofs.close();
                string path = createNewFile();
                _ofs.open(path, ios::binary | ios::app);
                assert(_ofs.is_open());
            }
            _ofs.write(data, len);
            assert(_ofs.good());
        }

    private:
        //判断文件大小，超过指定大小则切换文件
        string createNewFile(){
            //1.获取系统时间戳
            time_t time = util::Date::getTime();
            
            //2.转化时间戳
            tm t;
            //将时间戳表示的时间存放到时间结构体中
            localtime_r(&time, &t);
            // string filename = _basename;
            // filename + to_string(t.tm_year)+to_string...

            stringstream filename;
            filename << _basename 
                     << t.tm_year + 1900 << "-" 
                     << t.tm_mon + 1 << "-" 
                     << t.tm_mday << "_" 
                     << t.tm_hour << ":" 
                     << t.tm_min << ":" 
                     << t.tm_sec << ".log";

            return filename.str();
        }

    private:
        string _basename;       //文件路径
        ofstream _ofs;      //操作句柄
        size_t _gapnum;     //时间段序号
        size_t _gap;    // 时间段大小
    };

    // //违背开闭原则
    // class SinkFactory{
    // public:
    //     static LogSink::ptr createSink(int type){
    //         switch(type){
    //             case 1:
    //                 return make_shared<outSink>();
    //         }
    //     }
    // };

    // //类模板+完美转化，对自定义内容进行截取并转化，构造对应的对象
    // template<class ST, class ...Args>
    // class SinkFactory{
    // public:
    //     LogSink::ptr createSink(Args &&...args){
    //         return make_shared<ST>(forward<Args>(args)...);
    //     }
    // };

   
    //函数模板+完美转发
    class SinkFactory{
    public: 
        template<class ST, class ...Args>
        //用右值引用实现完美转发（如果不用右值引用会无法接收临时对象）
        static LogSink::ptr createSink(Args &&...args){
            return make_shared<ST>(forward<Args>(args)...);
        }
    };
}


#endif